namespace hjc {
	export class Entity implements IPoolable {

		protected $id: string;
		protected $compMap: Map<string, Component>;
		public get id(): string { return this.$id; }
		constructor(id?: string, ...params: any[]) { }
		public init(id?: string, ...params: any[]): void {
			this.$id = null;
			this.$compMap = new Map();
			if (isString(id)) this.$id = id;
			game.manager.entity.add(this);
			this.add(Position, 0, 0)
				.add(Size, 0, 0);
		}
		public disable(...clazzs: Array<new () => Component>): Entity {
			clazzs.forEach(clazz => {
				let comp = this.my(clazz);
				if (!!comp) comp.enabled = false;
			});
			return this;
		}

		public enable(...clazzs: Array<new () => Component>): Entity {
			clazzs.forEach(clazz => {
				let comp = this.my(clazz);
				if (!!comp) comp.enabled = true;
			});
			return this;
		}

		public my<C extends Component>(clazz: new (...params: any[]) => C): C {
			return this.$compMap.get(utils.nameOfClazz(clazz)) as C;
		}

		public add<C extends Component>(clazz: new (...params: any[]) => C, ...params: any[]): Entity {
			let comp = this.my(clazz);
			if (!comp) {
				comp = game.claim(clazz, ...params);
				comp.entity = this;
				this.$compMap.set(utils.nameOfClazz(clazz), comp);
			} else comp.init(...params);
			return this;
		}

		public remove(clazz: new () => Component): Entity {
			let comp = this.my(clazz);
			if (!comp) game.free(comp);
			return this;
		}

		public has(...clazzs: Array<new () => Component | SingComp>): boolean {
			return clazzs.every(clazz => this.$compMap.has(utils.nameOfClazz(clazz)));
		}

		public get position(): Position { return this.my(Position); }
		public get destin(): Destin { return this.my(Destin); }
		public get size(): Size { return this.my(Size); }
		public get velocity(): Velocity { return this.my(Velocity); }
		public get astar(): AStar { return this.my(AStar); }
		public get sprite(): Sprite { return this.my(Sprite); }
		public get aoi(): Aoi { return this.my(Aoi); }
		public get scanner(): Scanner { return this.my(Scanner); }

		public static distance(from: Entity, to: Entity): number { return utils.disOfPoints(from.position.x, from.position.y, to.position.x, to.position.y); }
		public distTo(x: number, y: number): number;
		public distTo(target: Entity): number;
		public distTo(p1: Entity | number, p2?: number): number {
			if (numeric(p1) && numeric(p2)) return utils.disOfPoints(this.position.x, this.position.y, p1 as number, p2 as number);
			else if (p1 instanceof Entity) return Entity.distance(this, p1);
		}
		public horDistTo(target: Entity, considerW: boolean = true): number {
			let dis = Math.abs(this.position.x - target.position.x);
			if (considerW) dis -= (this.size.w + target.size.w); return dis > 0 ? dis : 0;
		}
		public verDistTo(target: Entity, considerH: boolean = true): number {
			let dis = Math.abs(this.position.y - target.position.y);
			if (considerH) dis -= (this.size.h + target.size.h); return dis > 0 ? dis : 0;
		}
		public dispose(): void {
			game.manager.entity.remove(this);
			this.$compMap.forEach(comp => comp.dispose());
			game.free(this);
			this.$compMap.clear();
		}
	}
}